#!/usr/bin/python3
# ******************************************************************************
# Copyright (c) Huawei Technologies Co., Ltd. 2020-2020. All rights reserved.
# licensed under the Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#     http://license.coscl.org.cn/MulanPSL2
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
# PURPOSE.
# See the Mulan PSL v2 for more details.
# Author: Jiachen Fan
# Create: 2021-3-19
# ******************************************************************************/
"""
dnf install rpmdevtools
"""
import os
import abc
import logging
import yaml
import subprocess

from src.util.requests_util import requests_get_content
from src.util.spec import Spec, replace_macros
from src.util.osc_utils import get_osc_pkg_nvr
from src.util.dnf_utils import dnf_list_src_version

VERSION_CONF_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                                 "config",
                                 "openEuler_branch.yaml")


class PkgVerInterface(metaclass=abc.ABCMeta):
    """
    interface for get version from
    repo(i.e. gitee/obs/repo.openeuler.org)
    Attributes:
        name: name
        branch: branch
    """

    def __init__(self, name, branch):
        self.name = name
        self.version = ""
        self.release = ""
        self.epoch = "0"
        self.branch = branch

    def get_vr(self):
        """
        get version and release
        Returns:
            formatted version release
        """
        if not self.version or not self.release:
            return "unknow"
        return "{ver}-{rel}" \
            .format(
            ver=self.version,
            rel=self.release
        )

    def get_evr(self):
        """
        get version and release epoch
        Returns:
            formatted version release epoch
        """
        if not self.version or not self.release:
            return "unknow"
        return "{epoch}{ver}-{rel}" \
            .format(
            epoch="" if self.epoch == "0" else (self.epoch + ":"),
            ver=self.version,
            rel=self.release
        )

    def __str__(self):
        """
        Returns a string as the description of the object
        Returns:

        """
        return self.get_evr()

    @abc.abstractmethod
    def parse_content(self):
        pass


class GiteePkgVer(PkgVerInterface):
    """
    get pkg version from src-openeuler/pkg -> pkg.spec in xxx branch
    parse spec to get Version:str, Release:str, Epoch:str(default 0)
    Attributes:
        name: name
        branch: branch
    """

    def __init__(self, name, branch):
        super(GiteePkgVer, self).__init__(name, branch)
        self.spec_format_url = "https://gitee.com/src-openeuler/{name}/raw/{branch}/{name}.spec".format(
            name=self.name,
            branch=self.branch
        )

    def _check_branch(self):
        """
        Check branch
        Returns:
            True or False
        """
        openEuler_branch = {}
        if not os.path.isfile(VERSION_CONF_FILE):
            logging.error("openEuler branch config file:[%s] not exist", openEuler_branch)
            return False
        with open(VERSION_CONF_FILE, "r") as f:
            openEuler_branch = yaml.safe_load(f)
        if not openEuler_branch or not openEuler_branch["openEuler_branch"].get(self.branch, None):
            logging.error("[%s] is not a openEuler branch name", self.branch)
            return False
        return True

    def _rpmspec_parse(self, file_name):
        """
        Spec file parsing
        Args:
            file_name: file name

        Returns:

        """
        if not os.path.isfile(file_name):
            logging.error("spec file %s not exist", os.path.basename(file_name))
            return

        cmd_list = ["rpmspec", "--parse", file_name]
        try:
            proc = subprocess.Popen(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
        except OSError as e:
            logging.error("oserror: %s", str(e))
            return None, None
        stdout, stderr = proc.communicate()
        returncode = proc.returncode
        proc.poll()
        if returncode == 0:
            inputspec = Spec.from_string(stdout.decode("utf-8", "ignore"))
        else:
            inputspec = Spec.from_file(file_name)
        self.version = replace_macros(inputspec.version, inputspec)
        self.release = replace_macros(inputspec.release, inputspec)

    def _parse_spec(self, content, savedir="/tmp/update-package", save=False):
        """
        Spec file parsing
        Args:
            content: content
            savedir: save dir
            save: save

        Returns:
            None
        """
        _ = not os.path.exists(savedir) and os.makedirs(savedir)
        file_name = os.path.join(savedir, "{}.spec".format(self.name))
        if os.path.isfile(file_name):
            os.remove(file_name)
        with open(file_name, "a") as f:
            f.write(content)
        self._rpmspec_parse(file_name)
        if not save and os.path.exists(file_name):
            os.remove(file_name)

    def parse_content(self):
        """
        Parsing the contents of the file
        Returns:
            None
        """
        if not self.name or not self.branch:
            logging.error("name or branch fields not exists")
            return
        if not self._check_branch():
            logging.error("check branch failed")
            return
        status_code, content = requests_get_content(self.spec_format_url, timeout=30)
        if not content:
            return
        self._parse_spec(content)


class OBSPkgVer(PkgVerInterface):
    """
    get pkg version from repo
    Attributes:
        name: name
        branch: branch
    """
    REPO_ARCH_DICT = {
        "arm": {
            "repo": "standard_aarch64",
            "arch": "aarch64"
        },
        "x86": {
            "repo": "standard_x86_64",
            "arch": "x86_64"
        }
    }

    def __init__(self, name, branch):
        super(OBSPkgVer, self).__init__(name, branch)

    def _check_parse_branch(self):
        pass

    def _get_repo_arch(self, arch_key):
        """
        get repo arch
        Args:
            arch_key: arch key

        Returns:

        """
        if arch_key not in self.REPO_ARCH_DICT:
            logging.warning("[%s] key is not support", arch_key)
            return "", ""
        return self.REPO_ARCH_DICT[arch_key].get("repo", ""), \
               self.REPO_ARCH_DICT[arch_key].get("arch", "")

    def parse_content(self):
        """
        parse content
        Returns:

        """
        self._check_parse_branch()
        arch_list = self.REPO_ARCH_DICT.keys()
        for key in arch_list:
            repo, arch = self._get_repo_arch(key)
            name, ver, rel = get_osc_pkg_nvr(self.name, self.branch, repo=repo, arch=arch)
            if ver:
                self.version, self.release = ver, rel
                break


class DNFPkgVer(PkgVerInterface):
    """
    dnf gets the package and parses it
    Attributes:
       name: name
       repo_list: repo_list
    """
    def __init__(self, name, repo_list):
        super(DNFPkgVer, self).__init__(name, repo_list)

    def parse_content(self):
        """
        Parse the content
        Returns:

        """
        self.epoch, self.version, self.release = dnf_list_src_version(self.name, self.branch)


class RepoPkgVer(object):
    """
    to simply dip
    use this case to get repo version and
    Attributes:
        repo_pkg_ver: repo pkg version
    """

    def __init__(self, repo_pkg_ver):
        self.repo_pkg_ver = repo_pkg_ver

    def parse_ver_info(self):
        """
        use this function to fill version info into repo_pkg_ver
        use this after init this function
        Returns:

        """
        self.repo_pkg_ver.parse_content()

    def get_version(self):
        """
        get pkg [version] from self.repo_pkg_ver
        Returns:

        """
        return self.repo_pkg_ver.version

    def get_release(self):
        """
        get pkg [release] from self.repo_pkg_ver
        Returns:

        """
        return self.repo_pkg_ver.release

    def get_epoch(self):
        """
        get pkg [epoch] from self.repo_pkg_ver
        Returns:

        """
        return self.repo_pkg_ver.epoch

    def get_vr(self):
        """
        get pkg [version-release] from self.repo_pkg_ver
        Returns:

        """
        return self.repo_pkg_ver.get_vr()

    def get_evr(self):
        """
        get pkg [epoch:version-release] from self.repo_pkg_ver
        Returns:

        """
        return self.repo_pkg_ver.get_evr()

    def __str__(self):
        """
        usage same as get_evr
        print(RepoPkgVer) -> [epoch:version-release]
        """
        return self.repo_pkg_ver.get_evr()


def compare_pkg_ver(pkg, origin_branch, new_branch, origin_repo=DNFPkgVer, new_repo=OBSPkgVer):
    """
    Compare the versions of the two packages
    Args:
        pkg: pkg
        origin_branch: origin_branch
        new_branch: new_branch
        origin_repo: origin_repo
        new_repo: new_repo

    Returns:
        True or False
    """
    old_pkg_repo = RepoPkgVer(origin_repo(pkg, origin_branch))
    new_pkg_repo = RepoPkgVer(new_repo(pkg, new_branch))
    old_pkg_repo.parse_ver_info()
    new_pkg_repo.parse_ver_info()
    logging.info("[{pkg}] origin version: [{ovr}] new version:[{nvr}]" \
        .format(
        pkg=pkg,
        ovr=old_pkg_repo.get_vr(),
        nvr=new_pkg_repo.get_vr()
    ))
    if old_pkg_repo.get_vr() == new_pkg_repo.get_vr():
        logging.warning("pkg version not changed")
        return False
    return True
